package cn.demoncat.util.mongo.dao;

import cn.demoncat.util.exception.ParamRuntimeException;
import cn.demoncat.util.lang.*;
import cn.demoncat.util.lang.entity.MapSo;
import cn.demoncat.util.lang.entity.Pager;
import cn.demoncat.util.mongo.constant.MongoConstant;
import cn.demoncat.util.mongo.doc.BaseGeoDoc;
import cn.demoncat.util.mongo.util.AggregationUtil;
import cn.demoncat.util.mongo.util.QueryUtil;
import cn.demoncat.util.mongo.util.UpdateUtil;
import org.bson.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.*;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;

import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;

/**
 * MongoDB客户端基础Dao
 * 
 * @param <T> 文档映射类
 * @param <P> 文档id类
 *
 * @author 延晓磊
 *
 * @since 2017年7月22日
 */
public abstract class BaseMongoDao<T,P extends Serializable> {
	 
	/**
	 * Spring Data MongoDB
	 */
	@Autowired
	protected MongoTemplate mongoTemplate;

	// 集合映射类
	private Class<T> collectionClass;

    /**
	 * 获取集合映射类
	 */
	public Class<T> getCollectionClass() {
		if (collectionClass == null) {
			collectionClass = ReflectClassUtil.getGeneric(this.getClass(), 0);
		}
		return collectionClass;
	}
	
	/**
	 * 集合名称
	 * 
	 * 默认：类名驼峰
	 * 指定：用 @Document("collectionName") 注解类
	 * 
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年8月28日
	 */
	public String getCollectionName() {
		return mongoTemplate.getCollectionName(getCollectionClass());
	}
	
	/**
	 * 执行MongoDB命令
	 * 
	 * @param jsonCommand JSON格式的命令语句
	 * 
	 * @return 执行结果
	 *
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public Document execute(String jsonCommand) {
		return mongoTemplate.executeCommand(jsonCommand);
	}
	
    /**
     * 保存
     * 
     * ID == null：插入，自动赋值String型ID为ObjectId类型的值
     * ID != null：存在时更新，不存在时插入
     *
     * @param o
     * 
     * @author 延晓磊
     *
     * @since 2017年7月22日
     */
	public T save(T o) {
		if(o == null) {
			throw ParamRuntimeException.NULL;
		}
		return mongoTemplate.save(o);
	}
	
	/**
	 * 批量保存
	 *
	 * ID == null：插入，自动赋值String型ID为ObjectId类型的值
     * ID != null：存在时更新，不存在时插入
     * 
	 * @param os
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public List<T> save(Collection<T> os) {
		if(os == null || os.isEmpty()) {
			throw ParamRuntimeException.NULL;
		}
		return os.stream().map(this::save).collect(Collectors.toList());
	}
 
	/**
	 * 插入
	 * 
	 * ID == null：插入，自动赋值String型ID为ObjectId类型的值
     * ID != null：存在时报错，不存在时插入
	 *
	 * @param o
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public T insert(T o) {
		if(o == null) {
			throw ParamRuntimeException.NULL;
		}
		return mongoTemplate.insert(o);
	}
	
	/**
	 * 批量插入
	 *
	 * @param os
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public List<T> insert(Collection<T> os) {
		if(os == null || os.isEmpty()) {
			throw ParamRuntimeException.NULL;
		}
		return (List<T>) mongoTemplate.insertAll(os);
	}
 
	/**
	 * 更新全部
	 *
	 * @param query		查询条件 {@link QueryUtil}
	 * @param update	更新字段 {@link UpdateUtil}
	 * 
	 * @return 修改数
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public long update(Query query,Update update){
		//查询条件
		if(query == null) query = new Query();
		//更新查询到的所有文档
		return mongoTemplate.updateMulti(query, update, getCollectionClass()).getModifiedCount();
	}
	
	/**
	 * 更新首个
	 *
	 * @param query		查询条件 {@link QueryUtil}
	 * @param update	更新字段 {@link UpdateUtil}
	 * 
	 * @return 修改数
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public long updateFirst(Query query,Update update){
		//查询条件
		if(query == null) query = new Query();
		//更新查询到的首个文档
		return mongoTemplate.updateFirst(query, update, getCollectionClass()).getMatchedCount();
	}
	
	/**
	 * 更新
	 * 
	 * @param id	ID
	 * @param o		更新对象，只更新非null字段
	 * @return 修改数
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020-9-9
	 */
	public long update(P id, T o) {
		ParamEmptyUtil.checkNull(id, o);
		return updateFirst(QueryUtil.idQuery(id), UpdateUtil.set(o));
	}
	
	/**
	 * 聚合更新
	 * 
	 * @param query		查询条件 {@link QueryUtil}
	 * @param update	更新字段 {@link UpdateUtil}
	 * 
	 * @return 更新数
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年8月28日
	 */
	public long update(Query query, AggregationUpdate update){
		//查询条件
		if(query == null) query = new Query();
		// 聚合更新
		return mongoTemplate.update(getCollectionClass()).matching(query).apply(update).all().getModifiedCount();
	}
	
	/**
	 * ID删除
	 *
	 * @param id
	 * 
	 * @return 删除数
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public long delete(P id) {
		if(id == null) {
			throw ParamRuntimeException.NULL;
		}
		return mongoTemplate.remove(QueryUtil.idQuery(id), getCollectionClass()).getDeletedCount();
	}
	
	/**
	 * 条件删除
	 *
	 * @param query		查询条件 {@link QueryUtil}
	 * 
	 * @return 删除数
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public long delete(Query query){
		//查询条件
		if(query == null){
			query = new Query();
		}
		//条件删除
		return mongoTemplate.remove(query, getCollectionClass()).getDeletedCount();
	}
 
	/**
	 * 全部删除
	 *
	 * @return 
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public long deleteAll() {
		return mongoTemplate.remove(new Query()).getDeletedCount();
	}
	
	/**
	 * ID查询是否存在
	 * 
	 * @param id
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public boolean exists(P id){
		if(id == null) {
			throw ParamRuntimeException.NULL;
		}
		return mongoTemplate.exists(QueryUtil.idQuery(id), getCollectionClass());
	}
  
	/**
	 * 条件查询是否存在
	 *
	 * @param query		查询条件 {@link QueryUtil}
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public boolean exist(Query query){
		//查询条件
		if(query == null){
			query = new Query();
		}
		//条件查询
		return mongoTemplate.exists(query, getCollectionClass());
	}
  
	/**
	 * 统计总数
	 *
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public long count(){
		return mongoTemplate.count(new Query(), getCollectionClass());
	}
	
	/**
	 * 统计总数
	 *
	 * @param query		查询条件 {@link QueryUtil}
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public long count(Query query){
		//查询条件
		if(query == null){
			query = new Query();
		}
		//条件查询
		return mongoTemplate.count(query, getCollectionClass());
	}
 
	/**
	 * ID查询
	 *
	 * @param id
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public T getById(P id){
		if(id == null) {
			throw ParamRuntimeException.NULL;
		}
		return mongoTemplate.findById(id, getCollectionClass());
	}
	
	/**
	 * 查询全部
	 *
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public List<T> findAll(){
		return mongoTemplate.findAll(getCollectionClass());
	}
	
	/**
	 * 条件查询
	 *
	 * @param query	查询条件 {@link QueryUtil}
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public List<T> findAll(Query query){
		//查询条件
		if(query == null){
			query = new Query();
		}
		//条件查询
		return mongoTemplate.find(query, getCollectionClass());
	}
  
	/**
	 * 条件查询（单个）
	 *
	 * @param query	查询条件 {@link QueryUtil}：如果带sort则limit 1
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public T findOne(Query query){
		//查询条件
		if(query == null){
			// 最后1个
			query = new Query().with(QueryUtil.getSort(false));
		}
		//条件查询
		return mongoTemplate.findOne(query, getCollectionClass());
	}

	/**
	 * 条件查询（最后1个）
	 *
	 * @param query	查询条件 {@link QueryUtil}：order by id DESC limit 1
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public T findLast(Query query){
		if (query != null) {
			query.with(QueryUtil.getSort(false));
		}
		return findOne(query);
	}

	/**
	 * 去重查询
	 *
	 * 注：不能限制排序和条数
	 *
	 * @param field			去重域
	 * @param fieldClass	域的类型
	 * @return [去重域的值]
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public <F>List<F> findDistinct(String field, Class<F> fieldClass){
		return mongoTemplate.findDistinct(field, getCollectionClass(), fieldClass);
	}
	
	/**
	 * 去重查询
	 *
	 * 注：不能限制排序和条数
	 *
	 * @param query	查询条件 {@link QueryUtil}
	 * @param field	去重域
	 * @param fieldClass	域的类型
	 * @return [去重域的值]
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public <F>List<F> findDistinct(Query query, String field, Class<F> fieldClass){
		//查询条件
		if(query == null){
			query = new Query();
		}
		//条件查询
		return mongoTemplate.findDistinct(query, field, getCollectionClass(), fieldClass);
	}
	
	/**
	 *  排序查询
	 * 
	 * @param query 	查询条件 {@link QueryUtil}
	 * @param sort  	排序方式
	 * @return	 结果集，最多10000条
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public List<T> findAll(Query query, Sort sort){
		//查询条件
		if(query == null) {
			query = new Query();
		}
		//排序方式
		if(sort != null){
			query.with(sort);
		}
		//分页条件
		query.with(QueryUtil.getPageRequest(0, Pager.MAX_PAGE_SIZE));
		return findAll(query);
	}

	/**
	 * 列表查询
	 *
	 * @param query 	查询条件 {@link QueryUtil}
	 * @param limit  	页长
	 * @param sort  	排序方式
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public List<T> findAll(Query query, Integer limit, Sort sort){
		//查询条件
		if(query == null) {
			query = new Query();
		}
		//排序方式
		if(sort != null){
			query.with(sort);
		}
		//限制条数
		query.limit(IntegerUtil.toDefault(limit, Pager.DEFAULT_PAGE_SIZE));
		return findAll(query);
	}

	/**
	 * 分页/排序查询
	 * 
	 * @param o 		查询条件（匹配对象的属性）
	 * @param pageNum  	页号
	 * @param pageSize  页长
	 * @param sorts  	排序方式 {字段名:0升序/1逆序}
	 * @return	 
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public List<T> findAll(T o, Integer pageNum, Integer pageSize, LinkedHashMap<String,Integer> sorts){
		return findAll(QueryUtil.build(o), pageNum, pageSize, sorts);
	}
	
	/**
	 * 分页/排序查询
	 * 
	 * @param query 	查询条件 {@link QueryUtil}
	 * @param pageNum  	页号
	 * @param pageSize  页长
	 * @param sorts  	排序方式 {标准SQL格式： xx ASC,yy DESC}
	 * @return	 
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public List<T> findAll(Query query, Integer pageNum, Integer pageSize, String sorts){
		return findAll(query, pageNum, pageSize, QueryUtil.getSort(sorts));
	}
	
	/**
	 * 分页/排序查询
	 * 
	 * @param query 	查询条件 {@link QueryUtil}
	 * @param pageNum  	页号
	 * @param pageSize  页长
	 * @param sorts  	排序方式 {字段名:0升序/1逆序}
	 * @return	 
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public List<T> findAll(Query query, Integer pageNum, Integer pageSize, LinkedHashMap<String,Integer> sorts){
		return findAll(query, pageNum, pageSize, QueryUtil.getSort(sorts));
	}
	
	/**
	 * 分页/排序查询
	 * 
	 * @param query 	查询条件 {@link QueryUtil}
	 * @param pageNum  	页号
	 * @param pageSize  页长
	 * @param sort  	排序方式
	 * @return	 
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public List<T> findAll(Query query, Integer pageNum, Integer pageSize, Sort sort){
		//查询条件
		if(query == null) {
			query = new Query();
		}
		//排序方式
		if(sort != null){
			query.with(sort);
		}
		//分页条件
		query.with(QueryUtil.getPageRequest(pageNum, pageSize));
		return findAll(query);
	}

	/**
	 * 分页/排序查询
	 * 
	 * @param o 		查询条件（匹配对象的属性）
	 * @param pageNum  	页号
	 * @param pageSize  页长
	 * @param sorts  	排序方式 {字段名:0升序/1逆序}
	 * @return	 
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public Pager<T> findPage(T o, Integer pageNum, Integer pageSize, LinkedHashMap<String, Integer> sorts){
		return findPage(QueryUtil.build(o), pageNum, pageSize, sorts);
	}
	
	/**
	 * 分页/排序查询
	 * 
	 * @param query 	查询条件 {@link QueryUtil}
	 * @param pageNum  	页号
	 * @param pageSize  页长
	 * @param sorts  	排序方式 {字段名:0升序/1逆序}
	 * @return	 
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public Pager<T> findPage(Query query,Integer pageNum,Integer pageSize,LinkedHashMap<String,Integer> sorts){
		return findPage(query,pageNum,pageSize,QueryUtil.getSort(sorts));
	}
	
	/**
	 * 分页/排序查询
	 * 
	 * @param query 	查询条件 {@link QueryUtil}
	 * @param pageNum  	页号
	 * @param pageSize  页长
	 * @param sorts  	排序方式 {标准SQL格式： xx ASC,yy DESC}
	 * @return	 
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public Pager<T> findPage(Query query,Integer pageNum,Integer pageSize,String sorts){
		return findPage(query,pageNum,pageSize,QueryUtil.getSort(sorts));
	}
	
	/**
	 * 分页/排序查询
	 * 
	 * @param query 	查询条件 {@link QueryUtil}
	 * @param pageNum  	页号(为负或大于最大页数时，查询尾页)
	 * @param pageSize  页长
	 * @param sort  	排序方式 
	 * @return	 
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年7月22日
	 */
	public Pager<T> findPage(Query query,Integer pageNum,Integer pageSize,Sort sort){
		//查询条件
		if(query == null) {
			query = new Query();
		}
		//查询总数
		long total = count(query);
		//排序方式
		if(sort != null){
			query.with(sort);
		}
		//初始化分页
		Pager<T> pager =  new Pager<>(pageNum, pageSize,total);
		//分页条件
		query.with(QueryUtil.getPageRequest(pager));
		pager.setList(findAll(query));
		return pager;
	}
	
	/**
	 * 附近查询
	 * 
	 * 注：POJO 继承 {@link BaseGeoDoc} 
	 * 
	 * @param query	查询条件 {@link QueryUtil}
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年5月15日
	 */
	public List<T> findNear(NearQuery query) {
		return mongoTemplate.geoNear(query, getCollectionClass()).getContent().stream().map(geo -> {
			// 坐标
			T o = geo.getContent();
			// 距离
			((BaseGeoDoc)(o)).setDis(geo.getDistance().getValue());
			return o;
		}).collect(Collectors.toList());
	}
	
	/**
	 * 聚合管道
	 * 
	 * @param aggregation	聚合操作，多个用 Aggregation.newAggregation(...)组合 
	 * @param out			输出结果的类型（包含输出结果中的字段；结果比较复杂，应根据测试结果进行定义和解析）
	 * 
	 * @return 聚合结果
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年8月29日
	 */
	public <O>AggregationResults<O> aggregate(Aggregation aggregation, Class<O> out) {
		// db.collectionName.aggregate( [ agg ] )
		 return mongoTemplate.aggregate(aggregation, getCollectionName(), out);
	}
	
	/**
	 * 聚合管道
	 * 
	 * @param aggregation	聚合操作，多个用 Aggregation.newAggregation(...)组合 
	 * 
	 * @return 聚合结果
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年8月29日
	 */
	public AggregationResults<T> aggregate(Aggregation aggregation) {
		// db.collectionName.aggregate( [ agg ] )
		return aggregate(aggregation, getCollectionClass());
	}
	
	/**
	 * 分组统计
	 * 
	 * @param cri			查询条件 {@link QueryUtil}
	 * @param countClass	返回类：封装count（Long类型）和分组字段
	 * @param keys			分组字段
	 * 
	 * @return [ {"key1":"110000","key2":"1","count":43 } ] 
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年5月16日
	 */
	public <E>List<E> groupCount(Criteria cri, Class<E> countClass, String ...keys){
		// 条件筛选
		MatchOperation match = cri == null ? null : AggregationUtil.match(cri);
		// 分组统计
		GroupOperation group = AggregationUtil.groupCount(keys);
		// 聚合查询
		AggregationResults<E> result = aggregate(match == null ? Aggregation.newAggregation(group) : Aggregation.newAggregation(match, group), countClass);
		// 解析结果（默认的解析结果result.getMappedResults不准确，所以通过原始结果result.getRawResults自行处理）
		List<E> list = new ArrayList<>();
		Map<String,Object> o;
		Document idDoc;
		for (Document doc : result.getRawResults().getList("results",Document.class)) {
			if (keys.length == 1) { 
				// 只有1个字段的分组，结果为：{_id=2020-09-15, count=4}
				list.add(MapUtil.toObj(MapSo.getInstance(keys[0],doc.get(MongoConstant.ID_FIELD),MongoConstant.COUNT_ATTR,LongUtil.cast(doc.get(MongoConstant.COUNT_ATTR))), countClass, true, false));
			}else {
				// 有n个字段的分组，结果为：{_id=Document{{handleStatus=100, triggerDay=2020-09-15}}, count=2} }
				o = new HashMap<>();
				o.put(MongoConstant.COUNT_ATTR, LongUtil.cast(doc.get(MongoConstant.COUNT_ATTR)));
				idDoc = doc.get(MongoConstant.ID_FIELD, Document.class);
				for (String key : keys) {
					o.put(key, idDoc.get(key));
				}
				list.add(MapUtil.toObj(o, countClass, true, false));
			}
		}
		return list;
	}
	
	/**
	 * 分组统计
	 * 
	 * @param countClass	返回类：封装count（Long类型）和分组字段
	 * @param keys			分组字段
	 * 
	 * @return [ {"key1":"110000","key2":"1","count":43 } ] 
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年5月16日
	 */
	public <E>List<E> groupCount(Class<E> countClass, String ...keys){
		return groupCount(null, countClass, keys);
	}
	
	/**
	 * 分组统计
	 * 
	 * @param cri			查询条件
	 * @param keys			分组字段
	 * 
	 * @return [ 实体类，拥有count（Long类型）和分组字段 ]
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年5月16日
	 */
	public List<T> groupCount(Criteria cri, String ...keys){
		return groupCount(cri, getCollectionClass(), keys);
	}
	
	/**
	 * 分组统计
	 * 
	 * @param keys			分组字段
	 * 
	 * @return [ 实体类，拥有count（Long类型）和分组字段 ]
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年5月16日
	 */
	public List<T> groupCount(String ...keys){
		return groupCount(null, getCollectionClass(), keys);
	}
	
	
}
